#ifndef _COMM_HPP
#define _COMM_HPP

#include <iostream>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/stat.h>

#define PATHNAME "."
#define PROJ_ID 0x66
// 共享内存的大小，一般建议是4KB的整数倍
// 系统分配共享内存是以4KB为单位的!!!    ---    内存划分内存块的基本单位Page
#define MAX_SIZE 4097 // 实际内核分配的空间大小是8KB，内核会向上取整。 --> 内核给你的，和你能用的，是两码事，给你分配了8KB的Page，但你只能用4097的大小

key_t getKey()
{
    key_t k = ftok(PATHNAME, PROJ_ID); // 使用同样的PATHNAME PROJ_ID，可以获取同样的key！
    if (k < 0)//做好查错处理
    {
        // cin, cout, cerr -> stdin, stdout, stderr -> 0, 1, 2
        // 前者底层是三个类，后者是三个文件流，cerr的缓冲区刷新策略和cout不同，cerr输出错误信息向标准错误类打印。
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(1);
    }
    return k;
}
int getShmHelper(key_t k, int flags)
{
    // k被创建出来，未来一定被设置进共享内存的描述属性，用k来标识该共享内存在内核当中的唯一性。 就像pid和PCB的关系一样，pid一定会被设置进PCB里面。

    // shmid vs k  <=>  fd vs inode 。 底层是inode，上层通过fd控制。底层是key，上层通过shmid控制。
    // 这样的设计模式方便宏观层面的解耦，内核层和用户层最好是两套机制，好处就是，内核层做出调整的时候，并不会影响用户层，如果内核调整影响上层，将会带来很大的麻烦
    int shmid = shmget(k, MAX_SIZE, flags);
    if (shmid < 0)
    {
        std::cerr << "getshmHelper: " << errno << ":" << strerror(errno) << std::endl; // cerr和cout的刷新策略不太一样，cout是行缓冲，cerr是无缓冲
        exit(2);
    }
    return shmid;
}
int getShm(key_t k)
{
    return getShmHelper(k, IPC_CREAT); // 如果共享内存存在，获取即可
}
int createShm(key_t k)
{
    umask(0);
    return getShmHelper(k, IPC_CREAT | IPC_EXCL | 0600); // 创建一个全新的共享内存
}
void *attachshm(int shmid)//挂接
{
    // 纯数字没有任何意义，必须得有类型才有意义
    // 10u;
    // 10L;
    // 10;//字面值，但编译器默认将其看作为int类型
    // 3.14f;

    void *mem = shmat(shmid, nullptr, 0); // linux 64 bits system: void * -> 8 bytes
    if ((long long)mem == -1L)//指针的大小是8bits，这里我们强转成long long
    {
        std::cerr << "shmat: " << errno << ":" << strerror(errno) << std::endl;
        exit(3); // 挂接一般不会失败，如果失败，则我们需要释放共享内存，此步骤可通过返回值来处理，返回到server时再判断是否需要释放shm。
    }
    return mem;
}
void detachshm(void *start)//去关联
{
    if (shmdt(start) == -1)
    {
        std::cerr << "shmdt: " << errno << " : " << strerror(errno) << std::endl;
    }
}
void delShm(int shmid)
{
    if (shmctl(shmid, IPC_RMID, nullptr) == -1)
    {
        std::cerr << "shmctl: " << errno << " : " << strerror(errno) << std::endl;
    }
}

#endif